/***************************************************************************
 *   Copyright (C) 2007-2010 by Lasse Liehu                                *
 *   lliehu@kolumbus.fi                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "qtloginoperation.h"

#include <QBuffer>
#include <QByteArray>
#include <QList>
#include <QListIterator>
#include <QTimer>

// HTTP & networking includes
#include <QUrl>
#include <QHttp>
#include <QHttpRequestHeader>
#include <QHttpResponseHeader>
#include <QSslError>

#include <QtDebug>

#include "googlelogindata.h"

//BEGIN QtLoginOperationPrivate
class QtLoginOperationPrivate
{
    public:
        QHttp *http;
        QBuffer *buffer;
        int httpReqId;
        QByteArray sidData;
};
//END QtLoginOperationPrivate

QtLoginOperation::QtLoginOperation(GoogleLoginData& loginData, QObject *parent)
        : LoginOperation(loginData, parent), d(new QtLoginOperationPrivate)
{
    d->http = 0;
    d->buffer = 0;
    d->httpReqId = 0;
}

QtLoginOperation::~QtLoginOperation()
{
    // Delete data
    if (d->buffer) d->buffer->close();
    delete d->buffer;
    delete d->http;

    // Delete d-pointer
    delete d;
}

void QtLoginOperation::start()
{
    initLogin();
}

void QtLoginOperation::initLogin()
{
    // Preconditions
    Q_ASSERT(d->http == 0);
    Q_ASSERT(d->sidData.isEmpty() );
    Q_ASSERT(d->buffer == 0);

    // Starting initializing
    emit updateStatus(Login::Initializing);

    // Creating HTTP object
    d->http = new QHttp;
    connect(d->http, SIGNAL(requestFinished(int, bool) ), this,
             SLOT(slotHttpRequestFinished(int, bool) ));
    connect(d->http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader& )),
             this, SLOT(readResponseHeader(const QHttpResponseHeader&) ));
    connect(d->http, SIGNAL(sslErrors(const QList<QSslError>&)), this,
            SLOT(handleSslErrors(const QList<QSslError>&) ));

    // Preparing the data buffer
    d->buffer = new QBuffer;
    d->buffer->open(QBuffer::ReadWrite);

    // Generating post data
    QByteArray postData = "\nservice=reader&Email=";
    postData += QUrl::toPercentEncoding(loginData().email() );
    postData += "&Passwd=";
    postData += QUrl::toPercentEncoding(loginData().password() );

    // Setting host, connection type & headers
    QUrl url("https://www.google.com/accounts/ClientLogin");
    d->http->setHost(url.host(), QHttp::ConnectionModeHttps);
    QHttpRequestHeader header("POST", url.toString() );
    header.setContentType("application/x-www-form-urlencoded");

    // Sending the request
    d->httpReqId = d->http->request(header, postData, d->buffer);
    emit updateStatus(Login::RequestSent);
}

void QtLoginOperation::handleLoginData()
{
    // Getting the SID
    emit updateStatus(Login::ParsingData);
    int pos = d->sidData.indexOf("SID");
    if (pos == -1)
    {
        loginData().setSid(""); // erasing the old sid
        emit setConclusion(Login::NoSidInDataError, "");
        emit updateStatus(Login::Completed);
        return;
    }
    pos += 4; // skip the 'SID='
    const char *data = d->sidData.data();
    QString SID;
    for (int i = pos; data[i] != '\n'; ++i)
    {
        SID += data[i];
    }
    loginData().setSid(SID);

    // Login succeeded
    emit setConclusion(Login::Success, "");
    emit updateStatus(Login::Completed);
}

void QtLoginOperation::slotHttpRequestFinished(int reqId, bool error)
{
    // Preconditions
    Q_ASSERT(d->httpReqId != 0);
    Q_ASSERT(d->http);
    Q_ASSERT(d->buffer);
    Q_ASSERT(d->sidData.isEmpty() );

    if (reqId != d->httpReqId) return;
    d->httpReqId = 0;

    if (error)
    {
        loginData().setSid(""); // erasing old sid
        switch (d->http->error() )
        {
            case QHttp::NoError:
                qDebug("Got QHttp::NoError when there should have been an "
                       "error!");
                break;
            case QHttp::Aborted: // in this case the conclusion is already set
                break;
            case QHttp::HostNotFound:
                emit setConclusion(Login::ConnectionError, tr("Couldn't connect; "
                                   "couldn't resolve the server name. Please "
                                   "check your Internet connection."));
                break;
            case QHttp::ConnectionRefused:
                emit setConclusion(Login::ConnectionError, tr("Couldn't connect. "
                                   "Please check your network connection."));
                break;
            case QHttp::UnexpectedClose:
                emit setConclusion(Login::ConnectionError, tr("The connection "
                                   "was closed unexpectedly. Please try "
                                   "again and check your Internet connection."));
                break;
            case QHttp::InvalidResponseHeader:
                emit setConclusion(Login::UnknownServerError, tr("The server "
                                   "sent an invalid response header."));
                break;
            case QHttp::WrongContentLength:
                emit setConclusion(Login::UnknownError, "");
                break;
            case QHttp::ProxyAuthenticationRequiredError:
                emit setConclusion(Login::ConnectionError, tr("Proxy server "
                                   "requires authentication."));
                break;
            case QHttp::AuthenticationRequiredError:
                emit setConclusion(Login::SthNeedsFixingError, tr("The web "
                                   "server requires authentication to "
                                   "complete the request."));
                break;
            case QHttp::UnknownError:
                emit setConclusion(Login::UnknownError, "");
                break;
        }
        emit updateStatus(Login::Completed);
    }
    else // no error
    {
        // Response data received
        emit updateStatus(Login::ResponseDataReceived);

        // Reading the response data
        if ( ! d->buffer->seek(0) )
        {
            qDebug("error: QtLoginOperation: Couldn't seek the data buffer.");
            loginData().setSid(""); // erasing old sid
            emit setConclusion(Login::UnknownError, "");
            emit updateStatus(Login::Completed);
            return;
        }
        d->sidData = d->buffer->readAll();
        QTimer::singleShot(0, this, SLOT(handleLoginData() ));
    }
}

void QtLoginOperation::readResponseHeader(const QHttpResponseHeader &header)
{
    // Preconditions
    Q_ASSERT(d->buffer);
    Q_ASSERT(d->http);
    Q_ASSERT(d->httpReqId != 0);
    Q_ASSERT(d->sidData.isEmpty() );

    emit updateStatus(Login::ResponseHeaderReceived);

    switch (header.statusCode() )
    {
        case 200: // OK
            break;
        case 301: // moved
        case 302: // redirect
        case 303: // redirect method
        case 401: // denied
        case 404: // not found
        case 405: // bad method
            emit setConclusion(Login::SthNeedsFixingError, "");
            break;
        case 403: // forbidden
            emit setConclusion(Login::AuthenticationError, "");
            break;
        case 408: // timeout
            emit setConclusion(Login::ConnectionError, tr("Connection timed out. "
                               "Please try again later."));
            break;
        case 500: // internal error
            emit setConclusion(Login::UnknownServerError, "");
        default:
            qDebug("error: QtLoginOperation: Received response %i",
                   header.statusCode() );
            emit setConclusion(Login::UnknownError, tr("Couldn't handle "
                               "server's response."));
    }
    if (header.statusCode() != 200) d->http->abort();
}

void QtLoginOperation::handleSslErrors(const QList<QSslError>& errors)
{
    if ( ! errors.isEmpty() )
    {
        QListIterator<QSslError> iter(errors);
        QString msg = tr("The following SSL errors happened: ");
        while (iter.hasNext() )
        {
            msg += iter.next().errorString();
            msg += "; ";
        }
        qDebug() << msg;
        emit setConclusion(Login::SslError, "");
        d->http->abort();
    }
}
